package com.example.result.crash

import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
import android.os.Looper
import android.os.Process
import android.util.Log
import android.widget.Toast
import com.example.result.BaseApp
import com.example.result.R
import com.example.result.preference.Config
import java.io.File
import java.io.FileOutputStream
import java.io.PrintWriter
import java.io.StringWriter
import java.io.Writer
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale


class CrashHandler : Thread.UncaughtExceptionHandler {
    private var mDefaultHandler: Thread.UncaughtExceptionHandler? = null
    private lateinit var mContext: BaseApp
    private val formatter: DateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.CHINESE)

    val infos = HashMap<String, String>()
    fun init(context: BaseApp) {
        mContext = context
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()
        Thread.setDefaultUncaughtExceptionHandler(this)
    }

    override fun uncaughtException(t: Thread, e: Throwable) {
        if (!handleException(e) && mDefaultHandler != null) {
            mDefaultHandler?.uncaughtException(t, e)
        } else {
            try {
                Thread.sleep(3000)
            } catch (e: InterruptedException) {
                Log.e(TAG, "FATAL : " + e.stackTraceToString())
            }
            mContext.activities.forEach {
                it.finish()
            }
            //退出程序
            Process.killProcess(Process.myPid())
            System.exit(0)
        }
    }

    fun handleException(ex: Throwable): Boolean {

        Thread {
            Looper.prepare()
            Toast.makeText(
                mContext,
                R.string.crash_prompt,
                Toast.LENGTH_SHORT
            ).show()
            Looper.loop()
        }.start()
        collectionDeviceInfo(mContext)
        Log.e(TAG, "FATAL : " + ex.stackTraceToString())
        saveCrashInfo2File(ex)
        return true
    }

    fun collectionDeviceInfo(context: Context) {
        try {

            val packageManager = context.packageManager
            val packageInfo =
                packageManager.getPackageInfo(context.packageName, PackageManager.GET_ACTIVITIES)
            infos["versionname"] = packageInfo.versionName
            infos["packagename"] = packageInfo.packageName
            infos["vesioncode"] = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                packageInfo.longVersionCode.toString()
            } else {
                packageInfo.versionName
            }
        } catch (e: Exception) {
            Log.e(TAG, "an error occured when collect crash info", e)
        }
        try {
            Build::class.java.declaredFields.forEach { field ->
                field.isAccessible = true
                infos[field.name] = field.get(null)?.toString() ?: ""
            }
        } catch (e: Exception) {
            Log.e(TAG, "an error occured when collect crash info", e)
        }
    }

    /**
     * 保存错误信息到文件中
     *
     * @param ex
     * @return  返回文件名称,便于将文件传送到服务器
     */
    private fun saveCrashInfo2File(ex: Throwable): String? {
        val sb = StringBuffer()
        for ((key, value) in infos) {
            sb.append("$key=$value\n")
        }
        val writer: Writer = StringWriter()
        val printWriter = PrintWriter(writer)
        ex.printStackTrace(printWriter)
        var cause = ex.cause
        while (cause != null) {
            cause.printStackTrace(printWriter)
            cause = cause.cause
        }
        printWriter.close()
        ex.printStackTrace()
        val result: String = writer.toString()
        sb.append(result)
        try {
            val timestamp = System.currentTimeMillis()
            val time: String = formatter.format(Date())
            val fileName = "crash-$time-$timestamp.log"
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                val dir = Config.getCrashDir(mContext)
                dir?.apply {
                    if (exists()) {
                        mkdirs()
                        val fos = FileOutputStream(path + File.separator + fileName)
                        fos.write(sb.toString().toByteArray())
                        fos.close()
                    }
                }

            }
            return fileName
        } catch (e: Exception) {
            Log.e(TAG, "an error occured while writing file...", e)
        }
        return null
    }

    companion object Instance {
        val INSTANCE = CrashHandler()
        val TAG = "CrashHandler"
    }
}